<?php
// +-------------------------------------------------------------------------+
// | PHP Version 5                                                           |
// +-------------------------------------------------------------------------+
// | ImaEngine - web development framework                                   |
// | Copyright (c) 2006-2007 Sardar Yumatov <ja.doma@gmail.com>              |
// +-------------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or modify it |
// | under the terms of the GNU Lesser General Public License as published   |
// | by the Free Software Foundation; either version 2.1 of the License, or  |
// | (at your option) any later version.                                     |
// |                                                                         |
// | This library is distributed in the hope that it will be useful, but     |
// | WITHOUT ANY WARRANTY; without even the implied warranty of              |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                    |
// | See the GNU Lesser General Public License for more details.             |
// |                                                                         |
// | You should have received a copy of the GNU Lesser General Public        |
// | License along with this library; if not, write to the Free Software     |
// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
// +-------------------------------------------------------------------------+

/**
* Basic template handler implementations.
* Each handler defined in this file may act as a wrapper of an another handler.
* A chain of handlers can be constructed for any purpose and functionality.
*
* All handlers implemented here don't hold any state info, thus a single instance
* may be be used as parent handler for many different user defined handlers.
*
* @package common.template
* @category Handler
* @version v1.0
* @author Sardar Yumatov <ja.doma@gmail.com>
* @copyright Copyright &copy; 2006-2007 Sardar Yumatov
*
* @PHP5only
*/

require_once('lib/template/AbstractTemplateDocument.php');

/**
 * Implements loading templates (include).
 * This class implements cross-template block call if template provider is available.
 * @author Sardar Yumatov <ja.doma@gmail.com>
 */
class CrossTemplateDocumentHandler extends AbstractTemplateDocumentHandler {

	/** Loaded template documents name=>[(provider, handler)*]
	 * @var array of AbstractTemplateDocument */
	private static $templates;

	/**
	 * Create new cross template handler.
	 * @param TemplateDocumentHandler $handler the parent handler or null if none
	 */
	public function __construct($handler=null) {
		parent::__construct($handler);
	}

	/**
	 * Tag handler.
	 *
	 * Methor recognizes <b>include</b> action which evaluates root block of a <tt>$markers[0]</tt> template.
	 * Provider supplied by current template will be used to load the template by name. All
	 * named parameters will be given to provider as parameters.
	 * Example:
	 *  - {%include: "my template"%} -- include "my template" using current provider
	 *  - {%include: "my template" skin="less"%} -- includ "my teplate", pass 'skin' parameter to te provider.
	 *
	 * Including a template is actually loading the template and evaluating the root block in $this handler.
	 * If a reffered block is in another template, then template will be loaded on demand. However
	 * most constructions that work with blocks don't provide otpional parameters for the template
	 * provider. If a template needs to be loaded with specific options (a specific skin, locale etc.)
	 * then <b>include-load</b> action may be used. This action is equivalent to <b>include</b>, but doesn't
	 * evaluate the root block.
	 * Example:
	 *  - {%include-load:  "my template" skin="less" %} -- load specific template and make it available, no output will be done
	 *
	 * All loaded templates will be cached by this handler instance.
	 *
	 * Method recognizes <b>block</b> action which executes a block named <tt>$markers[0]</tt> one time.
	 * By default current template is used. If template name is provided, then block from specific template
	 * will be evaluated (template will be loaded on demand). Template name is separted from the block name by
	 * ':' character. Special name '*' points to the root template. This means both these characters may not occur in
	 * block and template name (block/template will be simply not accessible/addresable). All parameters will
	 * be given to provider while loading tempalte if needed.
	 * Examples:
	 *   - {%block: "block name" %} -- executes "block name" from curent template
	 *   - {%block: "my template:block name" %} - execute "block name" from "my template"
	 *   - {%block: "my template:*" skin='less' %} -- equivalent to {%include: "my template" skin='less'%}
	 *   - {%block: "*"%} -- oops... this is probably endless recursion, the template will repeat itself (evaluate the root block again)
	 *
	 * This syntax for block names should be supported by all other handlers that work with templates.
	 * These handlers may simply extend this class. All loaded templates are shared accross all instances of this class,
	 * however this class is smart enough to distinguish one provider from another. This means if a template was
	 * loaded by different provider than one used by current template, then reffered template will be loaded again.
	 *
	 * If template or block isn't available, then warning will be triggered and output will be discarded. This makes debugging
	 * more difficult, but may be more usual to PHP4 developers.
	 *
	 * @param AbstractTemplateDocumentBlock $block current block
	 * @param string $action tag action
	 * @param array $markers markers in original order with nummeric and associative keys preserved
	 * @return boolean true if tag is recognized and processed, false to discard output
	 */
	public function processTag(AbstractTemplateDocumentBlock $block, $action, $markers) {
		switch ($action) {
			case 'include-load':
			case 'include': if(!isset($markers[0])) {
								trigger_error("Can't include template, provide name as first unnamed parameter!", E_USER_WARNING);
								return false;
							} elseif(is_null($tpl = $this->obtainTemplate($markers[0], $block, $markers))) {
								trigger_error("Can't load template named '{$markers[0]}', probably no provider or some error while loading!", E_USER_WARNING);
							}
							return ($action == 'include')? $tpl->processOutput($this): false; //discard output for load only
			case 'block': if(!isset($markers[0])) {
							    trigger_error("Can't evaluate block, provide name as first unnamed parameter!", E_USER_WARNING);
							    return false;
						  }
						  return $this->executeTemplateBlock($markers[0], $block, $markers);
		}
		return parent::processTag($block, $action, $markers);
	}


	/**
	 * Execute block by $name in $this handler.
	 *
	 * This method accepts block name that may contain template name and other info as defined by <tt>processTag()</tt> method.
	 * This method is intended to be used by subclasses or chained handlers that execute blocks.
	 *
	 * If different template is requested, but no provider is returned by $thisblock, then notice will be triggered and
	 * first loaded by another provider template with equal name will be used. If none is found, then method outputs nothing
	 * and triggers warning. If $params is set, then it will be given to provider returned by $thisblock as set with parameters.
	 * If $params is set and no provider is returned by $thisblock then warning will be generated and parameters will be ignored.
	 *
	 * @param string $name block name, may include template name
	 * @param AbstractTemplateDocumentBlock $thisblock current block as given in <tt>processTag()</tt>
	 * @param array $params parameters for template provider
	 * @return boolean true if block is found and evaluated, false otherwise
	 */
	public function executeTemplateBlock($name, AbstractTemplateDocumentBlock $thisblock, $params = null) {
		return (!is_null($bl = $this->getTemplateBlock($name, $thisblock, $params)))? $bl->processOutput($this): false;
	}

	/**
	 * Returns template block by $name.
	 * This method is equal to <tt>executeTemplateBlock()</tt> but doesn't evaluate the block.
	 * @see executeTemplateBlock()
	 * @param string $name block name, may include template name
	 * @param AbstractTemplateDocumentBlock $thisblock current block as given in <tt>processTag()</tt>
	 * @param array $params parameters for template provider
	 * @param AbstractTemplateDocumentBlock found block or null if none is found
	 */
	public function getTemplateBlock($name, AbstractTemplateDocumentBlock $thisblock, $params = null) {
		$name = explode(':', $name, 2);
		$template = sizeof($name) > 1? trim($name[0]): null;
		$name = trim(sizeof($name) > 1? $name[1]: $name[0]);
		//obtain template
		$template = is_null($template)? $thisblock->getTemplateDocument(): $this->obtainTemplate($template, $thisblock, $params);
		return $name == '*'? $template->getRootBlock(): $template->getBlock($name);
	}

	/**
	 * Check if template named $name is already loaded.
	 * Method trigger notice if $thisblock doesn't provide valid template provider. In this
	 * case method simply checks if template $name is loaded by any provider.
	 * @param sring $name template name
	 * @param AbstractTemplateDocumentBlock $thisblock used to retrieve template provider
	 * @return boolean true if template $name is loaded by provider of given block or is loaded by any provider
	 */
	public function isTemplateLoaded($name, AbstractTemplateDocumentBlock $thisblock) {
		if($thisblock->getTemplateDocument()->getName() == $name) return true;
		if(is_null($provider = $thisblock->getTemplateDocument()->getProvider())) {
			trigger_error("Template '{$name}' is checked for loading, but current template doesn't return a valid provider!", E_USER_NOTICE);
			return isset(self::$templates[$name]); //use first one
		} else {
			if(isset(self::$templates[$name])) { //find matching provider
				$take = false;
				foreach(self::$templates[$name] as $ent) {
					if($take) return true;
					if($ent instanceof TemplateDocumentProvider && $ent->isEqual($provider)) $take = true;
				}
			}
			return false;
		}
	}

	/**
	 * Returns/loads template by $name.
	 * Method tries to find template $name in cache loaded by provider retuned by $thisblock.
	 * If none is found, then template will be loaded by the provider in previous step.
	 * Additional $params will be given to provider as is.
	 *
	 * If $thisblock doesn't return valid template provider, then notice will be triggered first,
	 * and if template isn't loaded, then warning will be triggered and method will return null.
	 * If $params is set and no provider is returned by $thisblock then warning will be generated and
	 * parameters will be ignored.
	 *
	 * @param string $name template name
	 * @param AbstractTemplateDocumentBlock $thisblock current block as given in <tt>processTag()</tt>
	 * @param array $params parameters for template provider
	 * @return AbstractTemplateDocument loaded template document or null if not loaded
	 */
	public function obtainTemplate($name, AbstractTemplateDocumentBlock $thisblock, $params = null) {
		if($thisblock->getTemplateDocument()->getName() == $name) return $thisblock->getTemplateDocument();
		if(is_null($provider = $thisblock->getTemplateDocument()->getProvider())) {
			trigger_error("Template '{$name}' needs to be loaded, but current template doesn't return a valid provider! Probably including will fail...", E_USER_NOTICE);
			if(!is_null($params)) trigger_error("Can't pass parameters to the provider while loading template '{$name}', there is no provider in current template!", E_USER_WARNING);
			//use first one
			if(isset(self::$templates[$name])) return self::$templates[$name][1];
			trigger_error("Can't obtain template '{$name}', there is no provider in current template and no other provider has loaded this template before!", E_USER_WARNING);
			return null;
		} else {
			if(isset(self::$templates[$name])) { //find matching provider
				$take = false;
				foreach(self::$templates[$name] as $ent) {
					if($take) return $ent;
					if($ent instanceof TemplateDocumentProvider && $ent->isEqual($provider)) $take = true;
				}
			}
			//load template
			try {
				$tpl = $provider->getTemplate($name, $params);
				self::$templates[$name][]=$tpl->getProvider(); //may be different than this provider
				self::$templates[$name][]=$tpl;
			} catch (Exception $e) {
				trigger_error("Can't obtain template '{$name}', error: {$e->getMessage()}", E_USER_WARNING);
				return null;
			}
			return $tpl;
		}
	}
}

/**
 * Implements basic functionality commonly used in templates.
 *
 * This class introduces "get value" call that may be used by any other chained handler
 * to obtain values in current context. In most cases you will extend this class
 * by your handlers.
 *
 * @author Sardar Yumatov <ja.doma@gmail.com>
 */
class BaseTemplateDocumentHandler extends CrossTemplateDocumentHandler {

	/**
	 * Create new basic handler.
	 * @param TemplateDocumentHandler $handler the parent handler or null if none
	 */
	public function __construct($handler=null) {
		parent::__construct($handler);
	}

	/**
	 * Basic handler implementation.
	 *
	 * Method recognizes <b>marker</b> action and delegates finding the value for <tt>$markers[0]</tt>to the subcass
	 * or to the wrapped object. Therefore a new overridable method <tt>getValueForMarker()</tt> is defined,
	 * which provides the value by key. Soome markers are recognized by this method directly:
	 *   - 'template' -- echo current template name
	 *   - 'blockname' -- echo current block name
	 *
	 * Other markers may be recognized by <tt>getValueForMarker()</tt>.
	 *
	 * Method recognizes <b>if</b> action with <tt>$markers[0]</tt> as condition and <tt>$markers[1]</tt> or <tt>$markers['block']</tt>
	 * as block name. The block will be executed in context of $this handler if <tt>getValueForMarker()</tt> returns true (!empty()).
	 * By default expressions are not supported, however subclasses may recognize expression and evaluae it (bad idea).
	 * The block name may contain template name as defined by <tt>CrossTemplateDocumentHandler</tt>. All named parameters except
	 * 'block' will be provided as is to the parent handler. Example:
	 *   - <tt>{%if: 'condition name' 'block to evaluate'%}</tt> -- evaluate block 'block to evaluate' if value for 'condition name' is true
	 *   - <tt>{%if: 'condition' block='template name:*'%}</tt> -- include template 'template name' if 'condition' is true
	 *
	 * Method recognizes <b>environment</b> and <b>locale</b> actions which echo <tt>$environment[$markers[0]]</tt>
	 * and <tt>$locale[$markers[0]]</tt> respectively. All other markers will be ignored.
	 *
	 * @param AbstractTemplateDocumentBlock $block the current block instance being processed
	 * @param string $action the action name (mark, include, process etc)
	 * @param string $markers the list of markers defined by the user
	 * @return boolean true if output should be used, false if output should be discarded
	 */
	public function processTag(AbstractTemplateDocumentBlock $block, $action, $markers) {
		global $environment, $locale;
		switch($action) {
			case 'environment': if(!isset($markers[0])) {
									trigger_error("Can't echo 'environment' variable, no non-named marker is specified!", E_USER_WARNING);
									return false;
								}
								echo isset($environment[$markers[0]])? $environment[$markers[0]]: '';
								return true;
			case 'locale': if(!isset($markers[0])) {
									trigger_error("Can't echo 'locale' variable, no non-named marker is specified!", E_USER_WARNING);
									return false;
								}
								echo isset($locale[$markers[0]])? $locale[$markers[0]]: '';
								return true;
			case 'marker': if(!isset($markers[0])) {
									trigger_error("Can't echo 'marker' variable, no non-named marker is specified!", E_USER_WARNING);
									return false;
							}
							switch ($markers[0]) {
								case 'template': echo $block->getTemplateDocument()->getName(); break;
								case 'blockname': echo $block->getName(); break;
								default: if(!is_null($val = $this->getValueForMarker($markers[0], $action, 'string'))) echo $val;
							}
							return true;

			case 'if': if(!isset($markers[0]) || (!isset($markers[1]) && !isset($markers['block']))) {
							trigger_error("Wrong parameter count for 'if' tag, condition(1) and block_name(2) are expected only!", E_USER_WARNING);
							return false;
					   }
					   if(is_null($val = $this->getValueForMarker($markers[0], $action, 'boolean'))) return false;
					   $bname = isset($markers[1])? $markers[1]: $markers['block'];
					   unset($markers['block'], $markers[1], $markers[0]);
					   return (!empty($val))? $this->executeTemplateBlock($bname, $block, $markers): false;
		}
		return parent::processTag($block, $action, $markers);
	}

	/**
	 * Returns value for the given marker.
	 * The method should return value for $name in requested $type if recognized.
	 * Predefined types are: 'number', 'string' and 'boolean'. However subclasses may expect
	 * other types if needed.
	 *
	 * By default method recognizes following markers:
	 *   -  'tagopen' -- returns '{%'
	 *   -  'tagclose' -- returns '%}'
	 *
	 * This method should be overriden by the subclasses providing values for different handlers.
	 * If nothing is recognized then method call's wrapped handler if it is an <tt>BaseTemplateDocumentHandler</tt>.
	 * This means recursieve search will stop at first non <tt>BaseTemplateDocumentHandler</tt> instance.
	 *
	 * @param string $name name of the variable
	 * @param string $action the tags action
	 * @param string $type expected type: 'string', 'boolean', 'iterator' etc.
	 * @return mixed the valueor null if value can't be obtained by some reasons
	 */
	public function getValueForMarker($name, $action, $type='string') {
		switch($name) {
			case 'tagopen': return '{%';
			case 'tagclose': return '%}';
		}
		//this aproach is real shit... will stop at first non base handler...
		return ($this->parentHandler instanceof BaseTemplateDocumentHandler)? $this->parentHandler->getValueForMarker($name, $action, $type): null;
	}
}

/**
 * Implements 'foreach' like construction.
 * Class implements following actions:
 *   - foreach -- iterates over an Iterator, does nothing if value isn't an iterable
 *   - foreach.echo --iterates over an iterator or echo value if it is not an iterable
 *   - apply -- applies the value to the current block, does nothing if value isn't an iterable
 *   - apply.echo -- same as 'apply', but echo's the value if it is not a iterable
 *   - first --
 * @author Sardar Yumatov <ja.doma@gmail.com>
 */
//class IterableBlockTemplateDocumentHandler extends BasicTemplateDocumentHandler {
?>